package com.itgacl.magic4j.libcommon.component.license.crypto;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 通过RSA方式，对文本内容进行非对称加密和解密
 */
public class RSAUtils {
	/**
	 * 非对称加密密钥算法
	 */
	public static final String KEY_ALGORITHM_RSA = "RSA";

	/**
	 * 公钥
	 */
	private static final String RSA_PUBLIC_KEY = "RSAPublicKey";

	/**
	 * 私钥
	 */
	private static final String RSA_PRIVATE_KEY = "RSAPrivateKey";

	/**
	 * RSA密钥长度
	 * 默认1024位，
	 * 密钥长度必须是64的倍数，
	 * 范围在512至65536位之间。
	 */
	private static final int KEY_SIZE = 1024;

	/**
	 * 私钥解密
	 *
	 * @param key  私钥
	 * @param data 待解密数据
	 * @return 解密后二进制
	 * @throws Exception 错误
	 */
	public static byte[] decryptPrivate(byte[] key, byte[] data) throws Exception {
		// 取得私钥
		PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(key);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM_RSA);
		// 生成私钥
		PrivateKey privateKey = keyFactory.generatePrivate(pkcs8);
		// 对数据解密
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE, privateKey);
		int blockSize = cipher.getBlockSize();
		if (blockSize > 0) {
			ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
			int j = 0;
			while (data.length - j * blockSize > 0) {
				bout.write(cipher.doFinal(data, j * blockSize, blockSize));
				j++;
			}
			return bout.toByteArray();
		}
		return cipher.doFinal(data);
	}

	/**
	 * 公钥解密
	 *
	 * @param key  公钥
	 * @param data 待解密数据
	 * @return 公钥字节
	 * @throws Exception 错误
	 */
	public static byte[] decryptPublic(byte[] key, byte[] data) throws Exception {
		// 取得公钥
		X509EncodedKeySpec x509 = new X509EncodedKeySpec(key);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM_RSA);
		// 生成公钥
		PublicKey publicKey = keyFactory.generatePublic(x509);
		// 对数据解密
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE, publicKey);
		return cipher.doFinal(data);
	}

	/**
	 * 公钥加密
	 *
	 * @param key  公钥
	 * @param data 待加密数据
	 * @return 公钥加密后的二进制数据
	 * @throws Exception 错误
	 */
	public static byte[] encryptPublic(byte[] key, byte[] data) throws Exception {
		// 取得公钥
		X509EncodedKeySpec x509 = new X509EncodedKeySpec(key);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM_RSA);
		PublicKey publicKey = keyFactory.generatePublic(x509);
		// 对数据加密
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE, publicKey);
		int blockSize = cipher.getBlockSize();
		if (blockSize > 0) {
			int outputSize = cipher.getOutputSize(data.length);
			int leavedSize = data.length % blockSize;
			int blocksSize = leavedSize != 0 ? data.length / blockSize + 1 : data.length / blockSize;
			byte[] raw = new byte[outputSize * blocksSize];
			int i = 0, remainSize = 0;
			while ((remainSize = data.length - i * blockSize) > 0) {
				int inputLen = remainSize > blockSize ? blockSize : remainSize;
				cipher.doFinal(data, i * blockSize, inputLen, raw, i * outputSize);
				i++;
			}
			return raw;
		}
		return cipher.doFinal(data);
	}

	/**
	 * 私钥加密
	 *
	 * @param key  私钥
	 * @param data 待加密数据
	 * @return 私钥字节
	 * @throws Exception 错误
	 */
	public static byte[] encryptPrivate(byte[] key, byte[] data) throws Exception {
		// 取得私钥
		PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(key);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM_RSA);
		// 生成私钥
		PrivateKey privateKey = keyFactory.generatePrivate(pkcs8);
		// 对数据加密
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE, privateKey);
		int blockSize = cipher.getBlockSize();
		if (blockSize > 0) {
			int outputSize = cipher.getOutputSize(data.length);
			int leavedSize = data.length % blockSize;
			int blocksSize = leavedSize != 0 ? data.length / blockSize + 1 : data.length / blockSize;
			byte[] raw = new byte[outputSize * blocksSize];
			int i = 0, remainSize = 0;
			while ((remainSize = data.length - i * blockSize) > 0) {
				int inputLen = remainSize > blockSize ? blockSize : remainSize;
				cipher.doFinal(data, i * blockSize, inputLen, raw, i * outputSize);
				i++;
			}
			return raw;
		}
		return cipher.doFinal(data);
	}

	/**
	 * 取得私钥
	 *
	 * @param keymap 密钥Map
	 * @return 私钥
	 */
	public static Key privateKey(Map<String, Key> keymap) {
		return keymap.get(RSA_PRIVATE_KEY);
	}

	/**
	 * 取得私钥
	 *
	 * @param keymap 密钥Map
	 * @return 私钥字节数组
	 */
	public static byte[] privateKeyByte(Map<String, Key> keymap) {
		return keymap.get(RSA_PRIVATE_KEY).getEncoded();
	}

	/**
	 * 取得公钥
	 *
	 * @param keymap 密钥Map
	 * @return 公钥
	 */
	public static Key publicKey(Map<String, Key> keymap) {
		return keymap.get(RSA_PUBLIC_KEY);
	}

	/**
	 * 取得公钥
	 *
	 * @param keymap 密钥Map
	 * @return 公钥字节
	 */
	public static byte[] publicKeyByte(Map<String, Key> keymap) {
		return keymap.get(RSA_PUBLIC_KEY).getEncoded();
	}

	/**
	 * 初始化密钥
	 *
	 * @param arg 种子
	 * @return 密钥字节
	 * @throws Exception 错误
	 */
	public static Map<String, Key> genkeysMap(byte[] arg) throws Exception {
		// 实例化密钥对生成器
		KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM_RSA);
		// 初始化密钥对生成器
		keyPairGenerator.initialize(KEY_SIZE, new SecureRandom(arg));
		// 生成密钥对
		KeyPair keyPair = keyPairGenerator.generateKeyPair();
		// 公钥
		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
		// 私钥
		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
		// 封装密钥
		Map<String, Key> keyMap = new HashMap<String, Key>(2);
		keyMap.put(RSA_PUBLIC_KEY, publicKey);
		keyMap.put(RSA_PRIVATE_KEY, privateKey);

		return keyMap;
	}

	/**
	 * 初始化密钥
	 *
	 * @param arg 种子
	 * @return 密钥
	 * @throws Exception 错误
	 */
	public static Map<String, Key> genkeysMap(String arg) throws Exception {
		return genkeysMap(arg.getBytes());
	}

	/**
	 * 初始化密钥
	 *
	 * @return Map 密钥Map
	 * @throws Exception 错误
	 */
	public static Map<String, Key> genkeysMap() throws Exception {
		return genkeysMap(UUID.randomUUID().toString().getBytes());
	}
}
